
const Ci = Components.interfaces;

lucidapi.getXpcomService = function( classid, ifaceid )
{
    return Components.classes[classid].getService(Components.interfaces[ifaceid]);
}

const IS_WINDOWS = (lucidapi.getXpcomService("@mozilla.org/xre/app-info;1", "nsIXULRuntime").OS == "WINNT");

function lucid_getVersion()
{
    var em = lucidapi.getXpcomService("@mozilla.org/extensions/manager;1", "nsIExtensionManager");
    return em.getItemForID("lucidity@emergent.org").version;
}

const LUCID_VERSION = lucid_getVersion();

const NSIFileOutputStream = Components.Constructor("@mozilla.org/network/file-output-stream;1", "nsIFileOutputStream");

const NSILocalFile = Components.Constructor("@mozilla.org/file/local;1", "nsILocalFile","initWithPath");
const NSIConverterOutputStream = Components.Constructor("@mozilla.org/intl/converter-output-stream;1", "nsIConverterOutputStream");
const NSIConverterInputStream = Components.Constructor("@mozilla.org/intl/converter-input-stream;1", "nsIConverterInputStream");
const NSIFileInputStream = Components.Constructor("@mozilla.org/network/file-input-stream;1", "nsIFileInputStream");
const NSIBinaryInputStream = Components.Constructor("@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream");
const NSIBinaryOutputStream = Components.Constructor("@mozilla.org/binaryoutputstream;1", "nsIBinaryOutputStream");
const NSIFilePicker = Components.Constructor("@mozilla.org/filepicker;1", "nsIFilePicker");
const NSIXMLHttpRequest = Components.Constructor("@mozilla.org/xmlextras/xmlhttprequest;1","nsIXMLHttpRequest");
const NSIMutableArray = Components.Constructor("@mozilla.org/array;1","nsIMutableArray");
const NSISupportsString = Components.Constructor("@mozilla.org/supports-string;1","nsISupportsString");
const NSIWritableVariant = Components.Constructor("@mozilla.org/variant;1","nsIWritableVariant");

function lucid_getNativeStringBundle(src)
{
    var localeService = lucidapi.getXpcomService("@mozilla.org/intl/nslocaleservice;1","nsILocaleService");
    var appLocale =  localeService.getApplicationLocale();
    var stringBundleService = lucidapi.getXpcomService("@mozilla.org/intl/stringbundle;1","nsIStringBundleService");
    return stringBundleService.createBundle(src, appLocale);
}

lucidapi.__defineGetter__("i18n", function() {
    var nativebundle = lucid_getNativeStringBundle("chrome://lucidity/locale/lucidity.properties");
    return {
        getString: function(key)
        {
            return nativebundle.GetStringFromName(key);
        },
        getFormattedString: function(key,parms)
        {
            if (!parms)
                parms = [];
            return nativebundle.formatStringFromName(key,parms,parms.length);
        }
    };
});

lucidapi.createThrowable = function()
{
    var msg = "LucidError";
    try {
        msg = arguments[0];
        var args = Array.prototype.slice.call(arguments,1);
        if (args.length == 1 && typeof(args[0].splice) == "function")
            args = args[0];
        if (args)
        {
            msg = lucidapi.i18n.getFormattedString(msg,args);
        }
        else
        {
            msg = lucidapi.i18n.getString(msg);
        }
    } catch (e) {
        // the logging system depends on this so the reverse can't be true.
        Components.utils.reportError(e);
        Components.utils.reportError("msg: " + msg);
        Components.utils.reportError("args.length: " + msg);
        if (e.stack)
            Components.utils.reportError(e.stack);
    }
    var retval = new Error("" + msg);
    retval.stackdepth = 1;
    return retval;
}

lucidapi.config = {

    getLucidPrefBranch: function()
    {
        var prefs = lucidapi.getXpcomService("@mozilla.org/preferences-service;1","nsIPrefService");
        return prefs.getBranch("extensions.lucidity.");
    },

    getLucidPref: function( key )
    {
        try
        {
            var prefs = this.getLucidPrefBranch();
            var btype = prefs.getPrefType( key );
            switch (btype) {
                case Ci.nsIPrefBranch.PREF_STRING:
                    return prefs.getCharPref(key);
                    break;
                case Ci.nsIPrefBranch.PREF_INT:
                    return prefs.getIntPref(key);
                    break;
                case Ci.nsIPrefBranch.PREF_BOOL:
                    return prefs.getBoolPref(key);
                    break;
                case Ci.nsIPrefBranch.PREF_INVALID:
                default:
                    lucidapi.warn("Unknown pref type: %d", btype);
                    break;
            }
        }
        catch (e)
        {
            lucidapi.error(e);
            throw e;
        }
    },

    setLucidPref: function( key, val )
    {
        var prefs = this.getLucidPrefBranch();
        var btype = prefs.getPrefType( key );
        switch (btype) {
            case Ci.nsIPrefBranch.PREF_STRING:
                lucidapi.assert(typeof(val) == "string");
                return prefs.setCharPref(key, val);
                break;
            case Ci.nsIPrefBranch.PREF_INT:
                lucidapi.assert(typeof(val) == "number");
                return prefs.setIntPref(key, val);
                break;
            case Ci.nsIPrefBranch.PREF_BOOL:
                lucidapi.assert(typeof(val) == "boolean");
                return prefs.setBoolPref(key, val);
                break;
            case Ci.nsIPrefBranch.PREF_INVALID:
            default:
                lucidapi.warn("Unknown pref type: %d", btype);
                break;
        }
    }
};

lucidapi.config.__defineGetter__("default_cache_keypass", function() { return this.getLucidPref("default_cache_keypass"); } );
lucidapi.config.__defineGetter__("hide_expired", function() { return this.getLucidPref("hide_expired"); } );
lucidapi.config.__defineGetter__("noInPlaceResults", function() { return this.getLucidPref("noInPlaceResults"); } );
lucidapi.config.__defineGetter__("google_mail_autopop_sigkey", function() { return this.getLucidPref("google.mail.autopop.sigkey"); } );
lucidapi.config.__defineGetter__("google_mail_autopop_enckey", function() { return this.getLucidPref("google.mail.autopop.enckey"); } );
lucidapi.config.__defineGetter__("google_mail_enabled", function() { return this.getLucidPref("google.mail.enabled"); } );
lucidapi.config.__defineGetter__("google_mail_encryptbutton", function() { return this.getLucidPref("google.mail.encryptbutton"); } );
lucidapi.config.__defineGetter__("default_private_key", function() { return this.getLucidPref("default_private_key"); } );
lucidapi.config.__defineGetter__("lastVersion", function() { return this.getLucidPref("lastVersion"); } );
lucidapi.config.__defineSetter__("lastVersion", function(val) { this.setLucidPref("lastVersion",val); } );
lucidapi.config.__defineGetter__("default_signing_mode", function() { return this.getLucidPref("default_signing_mode"); } );
lucidapi.config.__defineGetter__("enable_keysync", function() { return this.getLucidPref("enable_keysync"); } );
lucidapi.config.__defineGetter__("key_src_url", function() { return this.getLucidPref("key_src_url"); } );
lucidapi.config.__defineGetter__("google_login", function() { return this.getLucidPref("google_login"); } );
lucidapi.config.__defineGetter__("confirm_keyring_changes", function() { return this.getLucidPref("confirm_keyring_changes"); } );
lucidapi.config.__defineSetter__("confirm_keyring_changes", function(val) { this.setLucidPref("confirm_keyring_changes",val); } );
lucidapi.config.__defineGetter__("default_textmode_encrypt", function() { return this.getLucidPref("default_textmode_encrypt"); } );
lucidapi.config.__defineGetter__("always_show_save", function() { return this.getLucidPref("always_show_save"); } );
lucidapi.config.__defineGetter__("loglevel", function() { return this.getLucidPref("loglevel"); } );
lucidapi.config.__defineGetter__("tracelevel", function() { return this.getLucidPref("tracelevel"); } );
lucidapi.config.__defineGetter__("override_gpg_protection", function() { return this.getLucidPref("override_gpg_protection"); } );
lucidapi.config.__defineGetter__("gpg_compat_mode", function() { return this.getLucidPref("gpg_compat_mode"); } );

lucidapi.config.__defineGetter__("keystore_path", function() {
    var retval = this.getLucidPref("keystore_path");
    if (!retval || retval.length < 1)
    {
        const dirsvc = lucidapi.getXpcomService("@mozilla.org/file/directory_service;1","nsIProperties");
        var dirprop = (IS_WINDOWS ? "AppData" : "Home");
        var dirfile = new NSILocalFile( dirsvc.get(dirprop, Ci.nsIFile).path );
        dirfile.appendRelativePath((IS_WINDOWS ? "" : ".") + (lucidapi.config.gpg_compat_mode ? "gnupg" : "lucidpg"));
        retval = dirfile.path;
    }
    return retval;
} );

/* http://developer.mozilla.org/en/docs/Writing_textual_data */
function saveFileData( fileobj, data, isbinary, append )
{
    // use 0x02 | 0x10 to open file for appending.
    const WRITE_MODE = (append ? 0x10 : 0x20) | 0x02 | 0x08 ;
    const WRITE_PERMISSION = 0600;
    try
    {
        lucidapi.assert(typeof(data) == "string");
        var parentdir = fileobj.parent;
        if (!parentdir.exists())
            parentdir.create(Ci.nsIFile.DIRECTORY_TYPE,0700);
        var fstream = new NSIFileOutputStream();
        var ostream = null;
        fstream.init(fileobj, WRITE_MODE, WRITE_PERMISSION, 0);
        if (isbinary)
        {
            ostream = new NSIBinaryOutputStream();
            ostream.setOutputStream(fstream);
            ostream.writeBytes(data,data.length);
        }
        else
        {
            ostream = new NSIConverterOutputStream();
            ostream.init(fstream, "UTF-8", 0, 0x0000);
            ostream.writeString(data);
        }
    }
    finally
    {
        lucid_safeclose(ostream);
        lucid_safeclose(fstream);
    }
}

function lucid_bytes2string(bytes)
{
    var cooked = [];
    for (var ii = 0; ii < bytes.length; ii++)
    {
        cooked.push(String.fromCharCode(bytes.charCodeAt(ii) & 0xff));
    }
    return cooked.join("");
}

/* http://developer.mozilla.org/en/docs/Reading_textual_data */
function lucid_loadFileData( fileobj, isbinary )
{
    var retval = "";
    try
    {
        var fstream = new NSIFileInputStream();
        var istream = null;
        lucidapi.assert(fileobj instanceof Ci.nsILocalFile);
        lucidapi.assert(fileobj.exists(), "File does not exist");
        fstream.init(fileobj, -1, -1, Ci.nsILocalFile.CLOSE_ON_EOF);
        if (isbinary)
        {
            istream = new NSIBinaryInputStream();
            istream.setInputStream(fstream);
            var bytes = istream.readBytes(istream.available());
            retval = lucid_bytes2string(bytes);
        }
        else
        {
            istream = new NSIConverterInputStream();
            istream.init(fstream, "UTF-8", 1024, 0xFFFD);
            var str = {};
            while (istream.readString(1024, str) != 0) {
                retval += str.value;
            }
        }
    }
    finally
    {
        lucid_safeclose(istream);
        lucid_safeclose(fstream);
    }
    return retval;
}

var PassphraseManager = PGPPassphraseProvider.extend(function(classref) { return {
    initialize: function()
    {
        classref.superClass.initialize.call(this);
    },

    reallyAskForPassphrase: function( keyid, algo )
    {
        var password = {value: null};
        var check = {value: lucidapi.config.default_cache_keypass};
        var prompts = lucidapi.getXpcomService("@mozilla.org/embedcomp/prompt-service;1","nsIPromptService");

        var title = lucidapi.i18n.getString("password.dialog.title");
        var algostr = "Unknown";
        switch (algo) {
            case 1:
            case 2:
            case 3:
                algostr = "RSA";
                break;
            case 16:
            case 20:
                algostr = "ElG";
                break;
            case 17:
                algostr = "DSA";
                break;
        }
        var question = lucidapi.i18n.getFormattedString("password.dialog.description",[keyid.substr(8),algostr]);
        var checkques = lucidapi.i18n.getString("password.dialog.save-password-checkbox");

        var retval = [null,false];
        if (prompts.promptPassword(null, title, question, password, checkques, check))
        {
            retval[0] = password.value;
            retval[1] = check.value ? true : false;
        }
        return retval;
    }

}});

var sm_passCache = new PassphraseManager();


lucidapi.clearCache = function()
{
    sm_passCache.clear();
}

lucidapi.isPassphraseCacheEmpty = function()
{
    return sm_passCache.isEmpty();
}


var LucidKeySet = PGPKeySet.extend(function(classref) {
    return {
        initialize: function(keystorePath)
        {
            classref.superClass.initialize.call(this);
            this.keystore_path = Boolean(keystorePath) ? keystorePath : "";
            this.m_cache = {};
        },
        reset: function()
        {
            this.m_cache = {};
//            lucidapi.info("reseting LucidKeySet");
        },
        getKeyRingFile: function( secret )
        {
            var gnupghome = Boolean(this.keystore_path) ? this.keystore_path : lucidapi.config.keystore_path;
            if (!(this.m_cache["keystorePath"] == gnupghome))
                this.reset();
            var nsifile = new NSILocalFile(gnupghome);
            var fileext = lucidapi.config.gpg_compat_mode ? ".gpg" : ".lpg";
            nsifile.appendRelativePath(secret ? "secring" + fileext : "pubring" + fileext);
            return nsifile;
        },
        getKeyRingData: function( secret )
        {
            var nsifile = this.getKeyRingFile(secret);
            var cacheKey = secret ? "secRingData" : "pubRingData";
            var retval = this.m_cache[cacheKey];
            if (!retval) {
                retval = "";
                if (nsifile.exists())
                    retval = lucid_loadFileData(nsifile,true);
                if (retval)
                    this.m_cache[cacheKey] = retval;
            }
            return retval;
        },
        isEmpty: function() {
            try
            {
                return !(Boolean(this.getKeyRingData(true)) || Boolean(this.getKeyRingData(false)));
            } catch(e) {
                lucidapi.error(e);
            }
            return false;
        },
        validateGPGProtection: function()
        {
            if (lucidapi.config.override_gpg_protection)
                return;
            var gnupghome = Boolean(this.keystore_path) ? this.keystore_path : lucidapi.config.keystore_path;
            var nsifile = new NSILocalFile(gnupghome);
            nsifile.appendRelativePath("trustdb.gpg");
            if (nsifile.exists())
                throw lucidapi.createThrowable("luciderr.gpgProtection");
        },
        setKeyRingData: function(pubdata,secdata,changeop)
        {
            this.validateGPGProtection();
            if ((!this.skipConfirm) && !lucidapi.promptConfirm("confirm." + changeop,[]))
                return false;

            if (pubdata)
            {
                var pubfile = this.getKeyRingFile(false);
                saveFileData(pubfile,pubdata,true);
            }
            if (secdata)
            {
                var secfile = this.getKeyRingFile(true);
                saveFileData(secfile,secdata,true);
            }
            this.reset();
            return true;
        }
    };
});


lucidapi.masterKeyset = new LucidKeySet();

lucidapi.hasArmorType = function(text,type)
{
    if (!text)
        return false;
    var header = "-----BEGIN PGP " + type + "-----";
    return text.indexOf(header) > -1;
}

lucidapi.encryptImpl = function(data,options)
{
    var keyset = lucidapi.masterKeyset;
//    lucidapi.debug(JSON.toString(options));
    if (options.sigKeyIds)
        options.passCache = sm_passCache;
    return pgp_encryptData(data, keyset, options);
}

lucidapi.decryptImpl = function(data,options)
{
    var keyset = lucidapi.masterKeyset;
    if (!options)
        options = {};
    options.passCache = sm_passCache;
    var result = pgp_decryptData(data, keyset, options);
    return result;
}

/*
 * Open the file selected and return the choosen file.
 * If no file is selected, null is returned.
 */
lucidapi.openFileSelector = function( win, folders, initialfolder )
{
    var mode = Ci.nsIFilePicker.modeOpen;
    if (folders)
    {
        mode = Ci.nsIFilePicker.modeGetFolder;
    }
    var fp = new NSIFilePicker();
    if (initialfolder)
        try
        {
            fp.displayDirectory = new NSILocalFile(initialfolder);
        }
        catch (e)
        {
            lucidapi.warn(e);
        }


//        var i18n = lucid_getNativeStringBundle();
    fp.init(win, lucidapi.i18n.getString('fileSelectorSelectFile'), mode);
    fp.appendFilters(Ci.nsIFilePicker.filterAll);
    return (fp.show() == Ci.nsIFilePicker.returnOK) ? fp.file.path : null;
};

lucidapi.saveThingWithPicker = function(win,text,filename)
{
    var fp = new NSIFilePicker();
    fp.init(win, null, Ci.nsIFilePicker.modeSave);
    fp.appendFilters(Ci.nsIFilePicker.filterText | Ci.nsIFilePicker.filterAll);
    if (filename)
        fp.defaultString = filename;
    var a = fp.show();
    if (a != Ci.nsIFilePicker.returnOK && a != Ci.nsIFilePicker.returnReplace)
        return false;

    var filePath = fp.file.path;
    //Need to remove the file before save
    var fileObj = new NSILocalFile(filePath);
    if (fileObj.exists())
        fileObj.remove(true);
    saveFileData(fileObj, text);
    return true;
}

lucidapi.generateKeys = function(userid,passphrase,keysize,subsize,expiresecs)
{
    if (!keysize)
        throw lucidapi.createThrowable("luciderr.badKeySize", "unspecified");
    if (!subsize)
        throw lucidapi.createThrowable("luciderr.badKeySize", "unspecified");
    return pgp_generateKeys(userid,passphrase,keysize,subsize,expiresecs);
}

lucidapi.loadThingWithPicker = function(win)
{
    var filePath = lucidapi.openFileSelector(win);
    if (filePath)
        return lucid_loadFileData(new NSILocalFile(filePath));
    return null;
};

lucidapi.openTab = function(url)
{
    var wm = lucidapi.getXpcomService("@mozilla.org/appshell/window-mediator;1","nsIWindowMediator");
    var mainWindow = wm.getMostRecentWindow("navigator:browser");
    var browser = mainWindow.getBrowser();
    browser.selectedTab = browser.addTab(url);
}

lucidapi.openPreferences = function()
{
    var ww = lucidapi.getXpcomService("@mozilla.org/embedcomp/window-watcher;1","nsIWindowWatcher");
    /* var win = */
    ww.openWindow(null, "chrome://lucidity/content/options.xul", "lucidityPreferences", "chrome,centerscreen,toolbar", null);
};

lucidapi.openViewSource = function(url,lineno)
{
    var array = new NSIMutableArray();

    var str      = new NSISupportsString();
    str.data     = url;

    array.appendElement(str,false);
    if (lineno)
    {
        array.appendElement(null,false);
        array.appendElement(null,false);
        var linenostr      = new NSISupportsString();
        linenostr.data     = "" + lineno;
        array.appendElement(linenostr,false);
    }

    var ww = lucidapi.getXpcomService("@mozilla.org/embedcomp/window-watcher;1","nsIWindowWatcher");
    ww.openWindow(null, "chrome://global/content/viewSource.xul", "viewSource", null, array);
};

lucidapi.openKeygenWizard = function(showintro)
{
    var args = new NSIMutableArray();

    var nsShowintro = new NSIWritableVariant();
    nsShowintro.setAsBool(Boolean(showintro));
    args.appendElement(nsShowintro,false);

    var ww = lucidapi.getXpcomService("@mozilla.org/embedcomp/window-watcher;1","nsIWindowWatcher");
    var features = "chrome, dialog, resizable=yes";
    if (!showintro)
        features += ", modal";
    features += ", centerscreen";
    ww.openWindow(null, "chrome://lucidity/content/keygen.xul", '', features,args);
}

function openEncryptDialog( params )
{
    var args = new NSIMutableArray();


    var str      = new NSISupportsString();
    str.data     = JSON.toString(params);

    args.appendElement(str,false);

    var nsShowintro = new NSIMutableArray();

    args.appendElement(nsShowintro,false);

    var ww = lucidapi.getXpcomService("@mozilla.org/embedcomp/window-watcher;1","nsIWindowWatcher");
    ww.openWindow(null,'chrome://lucidity/content/encrypt.xul', '', 'chrome, dialog, resizable=yes, modal, centerscreen', args);
    if (nsShowintro.length > 0)
    {
        return JSON.fromString(nsShowintro.queryElementAt(0,Ci.nsISupportsString).data);
    }
    return null;
}

function chooseEncryptionOptions( preselect, presig, forcebin, filename )
{
    if (!preselect)
        preselect = [""];

    var params = {
        preselect: preselect.join(" "),
        presig: presig ? presig : "",
        forcebin: forcebin ? forcebin : false,
        filename: filename
    }

    var results = openEncryptDialog( params );
    if (results && results.accepted)
        return results;
    return null;

};

lucidapi.formatSignatureResultMessage = function(siginfo)
{
    if (siginfo.verified)
        return lucidapi.i18n.getFormattedString("validSignInCrypt", [siginfo.keyid])
    return lucidapi.i18n.getFormattedString("invalidSignInCrypt", [siginfo.keyid])
}

lucidapi.openEditorWindow = function(text, siginfo)
{
    if (text == undefined)
        text = "";
    if (siginfo == undefined)
        siginfo = null;

    var params = {text: text, siginfo: siginfo};


    var args = Components.classes["@mozilla.org/array;1"].createInstance(Components.interfaces.nsIMutableArray);

    var nsShowintro = Components.classes["@mozilla.org/variant;1"].createInstance(Components.interfaces.nsIWritableVariant);
    nsShowintro.setAsAString(JSON.toString(params));
    args.appendElement(nsShowintro,false);

    var ww = lucidapi.getXpcomService("@mozilla.org/embedcomp/window-watcher;1","nsIWindowWatcher");
    ww.openWindow(null,'chrome://lucidity/content/editor.xul', '', 'chrome, dialog, resizable=yes, centerscreen', args).focus();
}

lucidapi.checkForUpgrade = function()
{
    var version = lucidapi.config.lastVersion;

    var versionAct = LUCID_VERSION;

    if (version != versionAct)
    {
        lucidapi.config.lastVersion = versionAct;
        var mode = version;
        if (version == "")
            mode = "New";

        var prefssvc = lucidapi.getXpcomService("@mozilla.org/preferences-service;1", "nsIPrefService");
        var rootpref = prefssvc.getBranch("");
        // extensions.checkCompatibility = false
        if (rootpref.getIntPref("dom.max_chrome_script_run_time") < 240)
            rootpref.setIntPref("dom.max_chrome_script_run_time", 240);
        if (rootpref.getIntPref("dom.max_script_run_time") < 240)
            rootpref.setIntPref("dom.max_script_run_time", 240);
//            lucidapi.config.confirm_keyring_changes = false;
        lucidapi.openKeygenWizard(true);
    }
}

lucidapi.promptError = function(e)
{
    try {
        lucidapi.error(e);
        var prompts = lucidapi.getXpcomService("@mozilla.org/embedcomp/prompt-service;1","nsIPromptService");
        var title = lucidapi.i18n.getString("error.title");
        prompts.alert(null, title, e.message);
    } catch (e2) {
        Components.utils.reportError(e2);
        if (e2.stack)
            Components.utils.reportError(e2.stack);
    }
}

lucidapi.promptAlert = function(msg,i18nargs)
{
    try {
        if (i18nargs)
            msg = lucidapi.i18n.getFormattedString(msg,i18nargs);
        var prompts = lucidapi.getXpcomService("@mozilla.org/embedcomp/prompt-service;1","nsIPromptService");
        var title = lucidapi.i18n.getString("alert.title");
        prompts.alert(null, title, msg);
    } catch (e2) {
        lucidapi.error(e2);
    }
}

lucidapi.promptConfirm = function(msg,i18nargs)
{
    try {
        if (i18nargs)
            msg = lucidapi.i18n.getFormattedString(msg,i18nargs);
        var prompts = lucidapi.getXpcomService("@mozilla.org/embedcomp/prompt-service;1","nsIPromptService");
        var title = lucidapi.i18n.getString("confirm.title");
        return prompts.confirm(null, title, msg);
    } catch (e2) {
        lucidapi.error(e2);
    }
}

lucidapi.promptToSave = function(format)
{
    try {
        if (!format)
            return false;
        if (format != 'b' && !lucidapi.config.always_show_save)
            return false;
        var prompts = lucidapi.getXpcomService("@mozilla.org/embedcomp/prompt-service;1","nsIPromptService");
        var flags = prompts.STD_YES_NO_BUTTONS;
        var formatstr = lucidapi.i18n.getString(format == 't' ? "format.text" : "format.binary");
        var msg = lucidapi.i18n.getFormattedString("savetofile.message",[formatstr]);
        var title = lucidapi.i18n.getString("savetofile.title");
        return prompts.confirmEx(null, title, msg, flags, "", "", "", null, {}) < 1;

    } catch (e2) {
        lucidapi.error(e2);
    }
}

lucidapi.exportKeys = function(win,secret,keyid)
{
    try {
        var keyset = lucidapi.masterKeyset;
        var keyChain = Boolean(secret) ? keyset.getSecretChain() : keyset.getPublicChain();
        for (var ii = 0; ii < keyChain.length; ii++)
        {
            var theKey = keyChain[ii];
            if (theKey.getKeyId() == keyid)
            {
                var data = theKey.serialize();
                lucidapi.openEditorWindow(pgp_armorWrap(data, secret ? "PRIVATE KEY BLOCK" : "PUBLIC KEY BLOCK"));
                break;
            }
        }
    } catch (e2) {
        lucidapi.error(e2);
    }
}

function formStack(depth) {
    if (!depth)
        depth = 0;
    var retval = "\n";
    var stack = Components.stack.caller.caller;
    while (stack && depth > 0)
    {
        stack = stack.caller;
        depth--
    }
    while (stack) {
        retval += "@" + stack.filename + ":" + stack.lineNumber + "\n";
        stack = stack.caller;
    }
    return retval;
}

function parseStack(fullstackstr,depth) {
    if (!depth)
        depth = 0;
    var stackarr = fullstackstr.split("\n");
    var retval = null;
    for (var ii = stackarr.length - 2; ii >= 0; ii--)
    {
        var stackstr = stackarr[ii];
        var atIdx = stackstr.lastIndexOf("@");
        if (atIdx < 0)
            break;
        var colIdx = stackstr.lastIndexOf(":");
        if (colIdx < 0)
            break;
        var locUrl = stackstr.substring(atIdx + 1,colIdx);
        if (!locUrl)
            break;
        var lineNum = parseInt(stackstr.substring(colIdx + 1),10);
        retval = { filename: locUrl, lineNumber: lineNum, caller: retval };
    }
    for (ii = 0; ii < depth; ii++)
    {
        retval = retval.caller;
    }
    return retval;
}

lucidapi.logmsgs = [];

lucidapi.logimpl = function(level,loggable)
{
    var msg = loggable.message;
    var stack = loggable.stack;
    var pstack = parseStack(stack?stack:formStack(1),loggable.stackdepth);
    var cs = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
    if (level < 2)
    {
        var se = Components.classes["@mozilla.org/scripterror;1"].createInstance(Components.interfaces.nsIScriptError);
        se.init("Lucidity: " + msg,
            pstack.filename, null,  pstack.lineNumber,
            0, level, "aCategory");
        cs.logMessage(se);
    }
    else
    {
        cs.logStringMessage("Lucidity: " + msg);
    }
    if (!Components.classes["@mozilla.org/thread-manager;1"].getService().isMainThread)
        return;
    var observerService = lucidapi.getXpcomService("@mozilla.org/observer-service;1", "nsIObserverService");
    var jsonmsg = JSON.toString({message:msg,level:level,stack:pstack,tstamp:(new Date()).getTime()});
    lucidapi.logmsgs.push(jsonmsg);
    observerService.notifyObservers(null,"lucid-logmsg",jsonmsg);
}

lucidapi.logEntriesToFile = function()
{
    try
    {
        var text = "";
        for (var ii = 0; ii < lucidapi.logmsgs.length; ii++)
        {
            var logmsg = JSON.fromString(lucidapi.logmsgs[ii]);
            var msg = logmsg.message;
            var stack = logmsg.stack;
            text += lucidapi.sprintf( "%d %d %s\n", logmsg.tstamp, logmsg.level, msg );
            while (stack)
            {
                text += "\t" + stack.filename + ":" + stack.lineNumber + "\n";
                stack = stack.caller;
            }
        }

        const dirsvc = lucidapi.getXpcomService("@mozilla.org/file/directory_service;1","nsIProperties");
        var logfile = new NSILocalFile( dirsvc.get("ProfD", Ci.nsIFile).path );
        logfile.appendRelativePath("lucidpg-" + (new Date()).getTime() + ".log");
        saveFileData( logfile, text, false, true );
    }
    catch (e)
    {
        Components.utils.reportError(e);
        if (e.stack)
            Components.utils.reportError(e.stack);
    }
}

var sysObserver = new (lucidapi.AbstractClass.extend(function(classref) {

    var skip = 1;

    function entropyCallback(event) {
        try {
            switch (event.type) {
                case "keypress":
                    pgp_updateEntropy(0);
                    break;
                case "mousemove":
                    if (skip-- > 1)
                        break;
                    skip = (Math.round(Math.random() * 255) & 255);
                    pgp_updateEntropy((event.screenX << 16) + event.screenY);
                    break;
            }
        } catch (e) {
            lucidapi.error(e)
        }
    }

    return {

        initialize: function()
        {
            classref.superClass.initialize.call(this);
            var observerService = lucidapi.getXpcomService("@mozilla.org/observer-service;1", "nsIObserverService");
            observerService.addObserver(this,"quit-application",false);
            observerService.addObserver(this,"domwindowopened",false);
            observerService.addObserver(this,"domwindowclosed",false);
            observerService.addObserver(this,"lucid-logmsg",false);
        },

        observe: function(subject,topic,data){
            switch (topic) {
                case "domwindowopened":
//                    this.hookupWindow(subject);
                    break;
                case "quit-application":
                    // todo shutdown and sync-related call here.
                    break;
                case "lucid-logmsg":
                    break;
            }
        },

        hookupWindow: function(win)
        {
            if (win instanceof Ci.nsIDOMJSWindow)
            {
                // just in case the internal XPCOM bootstrap process changes
                // and we might somehow be called twice for the same window.
                win.removeEventListener("keypress", entropyCallback, false);
                win.removeEventListener("mousemove", entropyCallback, false);

                // Now register our entropy gatherer on this window.
                win.addEventListener("keypress", entropyCallback, false);
                win.addEventListener("mousemove", entropyCallback, false);
            }
        }

}}));

lucidapi.CryptoOperator = lucidapi.AbstractClass.extend(function(classref) { return {

    initialize: function(win)
    {
        classref.superClass.initialize.call(this);
        win.addEventListener("load", function() { sysObserver.hookupWindow(win); }, false );
        this.m_win = win;
        this.registerAsController();
    },

    registerAsController: function()
    {
        this.m_win.controllers.appendController(this);
    },

    getInvocationTarget: function()
    {
        throw new Error("luciderr.abstractMethod");
    },

    handleInplaceResult: function(resultobj)
    {
        return false;
    },

    handleSuccessfulCrypt: function(resultobj)
    {
        var stuff = resultobj.m_result;
        var sigdata = resultobj.m_siginfo;
        if ((stuff) && lucidapi.promptToSave(resultobj.m_format))
        {
            lucidapi.saveThingWithPicker(this.m_win, stuff, resultobj.m_filename);
        }
        else
        {
            if (!lucidapi.config.noInPlaceResults && (this.handleInplaceResult(resultobj)))
                return;
            lucidapi.openEditorWindow(stuff,sigdata);
        }
    },

    invokeOperation: function(invokeopts) {
        try {
            var opname = invokeopts.opname;
            if (!invokeopts)
                throw lucidapi.createThrowable("luciderr.invalidArguments");
            var text = null;
            var filename = null;
            if (invokeopts.fromfile)
            {
                var filePath = lucidapi.openFileSelector(this.m_win);
                if (filePath)
                {
                    var localfile = new NSILocalFile(filePath);
                    filename = localfile.leafName;
                    text = lucid_loadFileData(localfile);
                }
                else
                {
                    return false; // user cancelled;
                }
            }
            else
            {
                text = this.getInvocationTarget();
            }
            if (!text)
            {
                lucidapi.promptAlert("luciderr.noData",[]);
                return false;
            }
            var options = {};
            switch (opname) {
                case "encrypt":
                case "lucidcmd_encrypt":
                case "lucidcmd_encryptfile":
                    var tryPosition = text.indexOf("-----BEGIN PGP MESSAGE-----");
                    if (tryPosition != -1)
                    {
                        if (!lucidapi.promptConfirm("alreadyCrypt",[]))
                            return false;
                    }
                    var chosenopts = chooseEncryptionOptions(invokeopts.preselect,invokeopts.presig,invokeopts.fromfile,filename);
                    if (!chosenopts)
                    {
                        return false; // user canceled;
                    }
                    lucidapi.assert(typeof(chosenopts.encKeyIds) == "string");
                    return this.handleSuccessfulCrypt(lucidapi.encryptImpl(text,chosenopts));
                case "decrypt":
                case "verify":
                case "lucidcmd_decrypt":
                case "lucidcmd_decryptfile":
                case "lucidcmd_verify":
                case "lucidcmd_verifyfile":
                    options = { verifyOnly: (opname == "verify") };
                    return this.handleSuccessfulCrypt(lucidapi.decryptImpl(text,options));
                case "import":
                case "lucidcmd_import":
                case "lucidcmd_importfile":
                    return lucidapi.masterKeyset.importKeyData(text, false);
                default:
                    throw lucidapi.createThrowable("luciderr.invalidOpname",invokeopts.opname);
            }
        } catch (e) {
            lucidapi.promptError(e);
        }
    },

    supportsCommand : function( cmd ) {
        switch (cmd) {
            case "lucidcmd_encrypt":
            case "lucidcmd_decrypt":
            case "lucidcmd_verify":
            case "lucidcmd_import":
            case "lucidcmd_encryptfile":
            case "lucidcmd_decryptfile":
            case "lucidcmd_verifyfile":
            case "lucidcmd_importfile":
            return true;
        }
        return false;
    },
    isCommandEnabled : function( cmd ) {
        var data = null;
        switch (cmd) {
            case "lucidcmd_encrypt":
                data = this.getInvocationTarget();
                return Boolean(data);
            case "lucidcmd_decrypt":
            case "lucidcmd_verify":
                data = this.getInvocationTarget();
                var decenabled = lucidapi.hasArmorType(data,"MESSAGE") ||
                                 lucidapi.hasArmorType(data,"SIGNED MESSAGE") ||
                                 lucidapi.hasArmorType(data,"SIGNATURE");
                return Boolean(decenabled);
            case "lucidcmd_import":
                data = this.getInvocationTarget();
                var impenabled = lucidapi.hasArmorType(data,"PRIVATE KEY BLOCK") ||
                                 lucidapi.hasArmorType(data,"PUBLIC KEY BLOCK");
                return Boolean(impenabled);
            case "lucidcmd_encryptfile":
            case "lucidcmd_decryptfile":
            case "lucidcmd_verifyfile":
            case "lucidcmd_importfile":
                return true;
        }
        return false;
    },
    doCommand : function( cmd ) {
        var fromfile = false;
        if (cmd.indexOf("file") > 0)
            fromfile = true;
        this.invokeOperation({opname:cmd,fromfile:fromfile});
    },
    onEvent : function( evt ) { }


}});

